/**
 * 
 */
package cz.cuni.mff.abacs.burglar.logics.objects.agents;

import cz.cuni.mff.abacs.burglar.logics.DataMap;
import cz.cuni.mff.abacs.burglar.logics.objects.BaseInterface;
import cz.cuni.mff.abacs.burglar.logics.objects.BaseObject;
import cz.cuni.mff.abacs.burglar.logics.objects.Room;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Inventory;
import cz.cuni.mff.abacs.burglar.logics.objects.items.InventoryImpl;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Item;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Key;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.*;
import cz.cuni.mff.abacs.burglar.logics.planning.instructions.Instruction;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;


/**
 * Simple agent with the common features of all instantiated agent classes.
 * 
 * <ul>
 *   <li>can move on walkable positions</li>
 *   <li>can pick up items</li>
 *   <li>can put down items</li>
 *   <li>can contain items</li>
 *   <li>can lock/unlock doors, boxes</li>
 *   <li>can activate/deactivate positions</li>
 * </ul>
 * 
 * @author abacs
 */
public abstract class BaseAgent extends BaseObject implements Inventory, Agent {
	
		
	// -------------------------------------------------------------------------
	
	
	/** The walkable position where the agent is standing.
	 * 
	 * -1 means not set.
	 * Changeable property. */
	private int _positionId = -1;
	
	
	/** 
	 * -1 means not set.
	 * Changeable property. */
	private int _roomId = -1;
	
	
	/** The state of the agent (WELL or STUNNED). */
	private State _state = State.WELL;
	
	
	/** 
	 * Items are immutable.
	 * Changeable property. */
	private final InventoryImpl _inventory = 
		new InventoryImpl();
	
	
	/** Instruction cue to execute. 
	 * 
	 * Instructions are immutable.
	 * Changeable property. */
	protected final List<Instruction> _instructions = 
		new ArrayList<Instruction>();
	
	
	/** 
	 * The beliefs of the agent.
	 * 
	 * Changeable property. */
	protected final BeliefBase _knowledge = new BeliefBase();
	
	
	/**
	 * The goal set of the agent.
	 */
	protected GoalBase _goals = null;
	
	
	// -------------------------------------------------------------------------
	// constructors:
	
	
	/**
	 * 
	 * 
	 * @param id
	 * @param type
	 * @param position
	 * @param goals
	 * @param referenceMap
	 */
	protected BaseAgent(
			int id,
			BaseObject.Type type,
			Position position,
			GoalBase goals,
			DataMap referenceMap
	) {
		super(id, type, referenceMap);
		
		this._positionId = position.getId();
		
		// set the room if possible:
		if(position.getRoom() != null)
			this._roomId = position.getRoom().getId();
		
		this._goals = goals;
	}
	
	
	/**
	 * Goals are not yet copied, it should be done in descendants.
	 * 
	 * @param original
	 * @param referenceMap
	 */
	protected BaseAgent(BaseAgent original, DataMap referenceMap) {
		super(original.getId(), original.getType(), referenceMap);
		
		this._positionId = original._positionId;
		
		this._roomId = original._roomId;
		
		this._state = original._state;
		
		this._inventory.copyChangeables(original._inventory);
		
		this._instructions.clear();
		for(Instruction instr : original._instructions)
			this._instructions.add(instr);
		
		this._knowledge.copyChangeables(original._knowledge);
	}
	
	
	// -------------------------------------------------------------------------
	
	
	@Override
	public Position getPosition() {
		return this._referenceMap.getPosition(this._positionId);
	}
	
	
	@Override
	public int getPositionId() {
		return this._positionId;
	}
	
	
	@Override
	public void setPosition(int walkableId) {
		this._positionId = walkableId;
	}
	
	
	@Override
	public void setPosition(Position walkable) {
		this.setPosition(walkable.getId());
	}
	
	
	// -------------------------------------------------------------------------
	// Inventory related:
	
	
	@Override
	public void addItem(Item item) {
		this._inventory.addItem(item);
	}
	
	
	@Override
	public void addItems(List<Item> items) {
		this._inventory.addItems(items);
	}
	
	
	@Override
	public Item removeItem(int id) {
		return this._inventory.removeItem(id);
	}
	
	
	@Override
	public boolean hasItem(int id) {
		return this._inventory.hasItem(id);
	}
	
	
	@Override
	public boolean hasItemOfType(BaseInterface.Type type) {
		return this._inventory.hasItemOfType(type);
	}
	
	
	@Override
	public int getItemIdOfType(Type type) {
		return this._inventory.getItemIdOfType(type);
	}
	
	
	@Override
	public List<Item> getItems() {
		return this._inventory.getItems();
	}
	
	
	// -------------------------------------------------------------------------
	
	
	@Override
	public boolean isInState(State state) {
		return this._state == state;
	}
	
	
	@Override
	public Guard.State getState() {
		return this._state;
	}
	
	
	@Override
	public void daze() {
		this._state = Guard.State.STUNNED;
	}
	
	
	// -------------------------------------------------------------------------
	// Agent interface:
	
	
	@Override
	public boolean moveTo(Position walkable) {
		
		if(walkable.isNeighbouring((BasePosition)this.getPosition()) == false){
			
			System.out.println(
					"- " + this.getId() +
					": Failed to move - " + walkable.getId() + 
					" is too far"
			);
			
			return false;
		}
		
		if(walkable.isTypeOf(BaseObject.Type.DOOR)){
			Door door = (Door)walkable;
			
			if(door.isClosed()){
				System.out.println(
						"- " + this.getId() +
						": Failed to move - " + walkable.getId() + 
						" is closed"
				);
				this.examine(walkable);
				return false;
			}
			
			System.out.println(
					"- " + this.getId() + 
					": Moved from " + this._positionId +
					" to " + walkable.getId()
			);
			this._positionId = walkable.getId();
			return true;
		}
		
		if(walkable.isTypeOf(BaseObject.Type.FLOOR)){
			
			System.out.print(
					"- " + this.getId() + 
					": Moved from " + this._positionId +
					" to " + walkable.getId()
			);
			
			if(this.getPosition().isTypeOf(BaseObject.Type.DOOR)){
				// went into another room:
				int oldRoomId = this.getRoomId();
				this._roomId = walkable.getRoomId();
				
				// look around in the new room:
				this.lookAround();
				
				System.out.println(
						" (Room changed from " + oldRoomId +
						" to " + this.getRoomId() + ')'
				);
			}else{
				System.out.println();
			}
			
			this._positionId = walkable.getId();
			return true;
		}
		
		System.out.println(
				"- " + this.getId() + 
				": Failed to move to " + walkable.getId() +
				", invalid destination type"
		);
		
		return false;
	}
	
	
	@Override
	public boolean open(Lockable lockable) {
		boolean result = lockable.open();
		this.examine((Position)lockable);
		
		if(result){
			System.out.println(
					"- " + this.getId() +
					": Opened " + ((Position)lockable).getId()
			);
		}else{
			System.out.println(
					"- " + this.getId() +
					": Failed to open - " + ((Position)lockable).getId() + 
					" is locked"
			);
		}
		
		return result;
	}
	
	
	@Override
	public boolean close(Lockable lockable) {
		boolean result = lockable.close();
		this.examine((Position)lockable);
		
		System.out.println(
				"- " + this.getId() +
				": Closed " + ((Position)lockable).getId()
		);
		
		return result;
	}
	
	
	@Override
	public boolean unlock(Lockable lockable, Key key) {
		boolean result = lockable.unlock(key.getId());
		this.examine((Position)lockable);
		
		if(result){
			System.out.println(
					"- " + this.getId() + 
					": Unlocked " + ((Position)lockable).getId() +
					" with " + key.getId()
			);
		}else{
			System.out.println(
					"- " + this.getId() + 
					": Failed to unlock " + ((Position)lockable).getId() +
					" with " + key.getId()
			);
		}
		return result;
	}
	
	
	@Override
	public boolean lock(Lockable lockable, Key key) {
		boolean result = lockable.lock(key.getId());
		this.examine((Position)lockable);
		
		if(result){
			System.out.println(
					"- " + this.getId() + 
					": Locked " + ((Position)lockable).getId() + 
					" with " + key.getId()
			);
		}else{
			System.out.println(
					"- " + this.getId() +
					": Failed to lock " + ((Position)lockable).getId() +
					" with " + key.getId()
			);
		}
		
		return result;
	}
	
	
	@Override
	public boolean pickUp(Inventory inventory, Item item) {
		Item result = inventory.removeItem(item.getId());
		if(result != null)
			this.addItem(item);
		this.examine((Position)inventory);
		
		this._knowledge.forgetItemPosition(item);
		
		if(result != null){
			System.out.println(
					"- " + this.getId() +
					": Picked up from " + ((Position)inventory).getId() + 
					" item " + item.getId()
			);
		}else{
			System.out.println(
					"- " + this.getId() +
					": Failed to pick up from " + ((Position)inventory).getId() + 
					" item " + item.getId()
			);
		}
		
		return result != null;
	}
	
	
	@Override
	public boolean use(Vender vender) {
		vender.deactivate();
		this.examine(vender);
		
		this.getGoals().removeVenderToVisit();
		
		System.out.println(
				"- " + this.getId() +
				": Used " + vender.getId()
		);
		
		return true;
	}
	
	
	// -------------------------------------------------------------------------
	
	@Override
	public Room getRoom() {
		return this._referenceMap.getRoom(this._roomId);
	}
	
	
	@Override
	public int getRoomId() {
		return this._roomId;
	}
	
	
	@Override
	public boolean isInRoom(Room room) {
		return this.isInRoom(room.getId());
	}
	
	
	@Override
	public boolean isInRoom(int roomId) {
		if(this._roomId == roomId)
			return true;
		return this.getPosition().isInRoom(roomId);
	}
	
	
	// -------------------------------------------------------------------------
	// instructions:
	
	
	@Override
	public void addInstruction(Instruction instruction) {
		this._instructions.add(instruction);
	}
	
	
	@Override
	public void addInstructions(List<Instruction> instructions) {
		this._instructions.addAll(instructions);
	}
	
	
	@Override
	public void clearInstructions() {
		this._instructions.clear();
	}
	
	
	@Override
	public List<Instruction> getInstructions() {
		return new ArrayList<Instruction>(this._instructions);
	}
	
	
	@Override
	public boolean hasInstructions() {
		return ! this._instructions.isEmpty();
	}
	
	
	@Override
	public Instruction popFirstInstruction() {
		if(this._instructions.isEmpty())
			return null;
		return this._instructions.remove(0);
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/**
	 * Returns the rooms that the agent plans to visit.
	 * 
	 * @return list of rooms to visit
	 */
	public List<Integer> getPlannedRoomIds() {
		List<Integer> rooms = new LinkedList<Integer>();
		int currentRoomId = -1;
		Position subject;
		
		for(Instruction instr : this._instructions){
			subject = this._referenceMap.getPosition(instr._subjectId);
			if(subject.getRoomId() != currentRoomId){
				currentRoomId = subject.getRoomId();
				rooms.add(currentRoomId);
			}
		}
		
		return rooms;
	}
	
	
	// -------------------------------------------------------------------------
	
	
	@Override
	public boolean lookAround() {
		boolean result = false;
		
		for(Agent agent : this.getRoom().getAgents()){
			
			// doesn't check self
			if(agent.getId() == this.getId())
				continue;
		
			boolean agentResult = this._knowledge.seenFromFar(agent);
			
			result = agentResult || result;
		}
			
		result = 
			this._knowledge.seenFromFar(this.getRoom().getOperablePositions()) ||
			result;
		
		return result;
	}
	
	
	/**
	 * Closely examines a selected position.
	 */
	public void examine(Position pos) {
		this._knowledge.seenFromNear(pos);
	}
	
	
	@Override
	public BeliefBase getBeliefBase() {
		return this._knowledge;
	}
	
	
	@Override
	public GoalBase getGoals() {
		return this._goals;
	}
	
	
	@Override
	public Agent examinedFromFar() {
		return this.copy(null);
	}
	
	
	@Override
	public boolean matchesFromFar(Agent agent) {
		BaseAgent other = (BaseAgent) agent;
		
		return	this.getId() == other.getId() &&
				//this._inventory.matchesFromFar(other._inventory) &&
				this.getRoomId() == other.getRoomId() &&
				this._state == other._state;
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/**
	 * Replicates the changeable details of the selected agent.
	 */
	protected void copyChangeables(BaseAgent original) {
		
		this._positionId = original._positionId;
		
		this._roomId = original._roomId;
		
		this._state = original._state;
		
		this._inventory.copyChangeables(original._inventory);
		
		this._instructions.clear();
		for(Instruction instr : original._instructions)
			this._instructions.add(instr);
		
		this._knowledge.copyChangeables(original._knowledge);
		
		this._goals.copyChangeables(original._goals);
	}
	
	
}
